package co.edu.udea.iw.prestamos.bl;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import co.edu.udea.iw.prestamos.modelo.dao.PrestamoDAO;
import co.edu.udea.iw.prestamos.modelo.dto.EstadoPrestamo;
import co.edu.udea.iw.prestamos.modelo.dto.Prestamo;
import co.edu.udea.iw.prestamos.modelo.dto.PrestamoList;
import co.edu.udea.iw.prestamos.modelo.dto.Respuesta;
import co.edu.udea.iw.prestamos.util.ConstantesEstaticas;
import co.edu.udea.iw.prestamos.util.exception.DatabaseException;
import co.edu.udea.iw.prestamos.util.exception.RegistryNotFoundException;
import co.edu.udea.iw.prestamos.util.logger.Log;
import co.edu.udea.iw.prestamos.util.ppt.LeerPropiedades;

/**
 * Lógica de negocio para la gestión de los préstamos.
 * 
 * @author Dave Rivera <daverivera90@gmail.com>
 * @since 16-07-2014
 * @version 1.0
 */
@Configurable
@Service("prestamoBL")
public class PrestamoBL {
	
	/**
	 * Se inyectan las dependencias de la clase.
	 */
	@Autowired
	private PrestamoDAO prestamoDao;
	
	@Autowired
	private Validator validator;
	
	@Autowired
	private LeerPropiedades leerPropiedades;
	
	/**
	 * Registra una solicitud de préstamo en el sistema. Si la información del
	 * préstamo pasada como parámetro es nula o está incompleta, se retorna
	 * una respuesta de error y no se realiza la operación.
	 * 
	 * @param prestamo Información del préstamo.
	 * @return respuesta con el resultado de la operación.
	 */
	@Transactional
	public Object realizarPrestamo(Prestamo prestamo){
		Respuesta respuesta = new Respuesta();
		
		if (prestamo == null){
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje( leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_SOLICITUD_ERROR_NULL) );
			return respuesta;
		}
		
		//Verificar que tenga todos los campos obligatorios
		Set< ConstraintViolation<Prestamo> > constraintViolations = validator.validate( prestamo );
		if (constraintViolations.size() > 0){
			Log.log(getClass(), leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.PRESTAMO_SOLICITUD_ERROR_MISSINGDATA) + ": " + constraintViolations.size(), Log.INFO, null);
			Log.log(getClass(), constraintViolations.toString(), Log.INFO, null);
			
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje(constraintViolations.toString());
			
			return respuesta;
		}
		
		try {
			Prestamo p = prestamoDao.crear(prestamo);
			
			if (p.equals(prestamo)){
				respuesta.setTipo(Respuesta.SUCCESS);
				respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, 
						             ConstantesEstaticas.PRESTAMO_SOLICITUD_SUCCESS));
				return respuesta;
			}
		} catch (DatabaseException e) {
			Log.log(getClass(), leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.PRESTAMO_SOLICITUD_ERROR_BD), Log.ERROR, e);
		}
		
		respuesta.setTipo(Respuesta.ERROR);
		respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_SOLICITUD_ERROR_BD));
		
		return respuesta;
	}
	
	/**
	 * Cambia el estado de un préstamo ya registrado en la base de datos.
	 * 
	 * @param id Identificación del préstamo.
	 * @param estado Identificación del nuevo estado del préstamo.
	 * @return respuesta con el resultado de la operación.
	 */
	public Object cambiarEstado(int id, int estado){
		Respuesta respuesta = new Respuesta();
		
		EstadoPrestamo estadoPrestamo = new EstadoPrestamo();
		estadoPrestamo.setId(estado);
		
		Prestamo prestamo;
		try {
			prestamo = prestamoDao.obtenerPorId(id);	
		} catch (RegistryNotFoundException e) {
			Log.log(getClass(), 
					leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_EXCEPCIONES, ConstantesEstaticas.PRESTAMO_EXCEPCION_CAMBIAR_ESTADO), 
					Log.ERROR,
					e
				);
			respuesta.setTipo(Respuesta.ERROR);
			respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_INVALID_ID));
			return respuesta;
		}
			
		prestamo.setEstadoPrestamo(estadoPrestamo);
		
		try {
			Prestamo p = prestamoDao.actualizar(prestamo);
			if (p.equals(prestamo)){
				respuesta.setTipo(Respuesta.SUCCESS);
				respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_ESTADO_UPDATE_SUCCESS));
				return respuesta;
			}
		} catch (DatabaseException e) {
			Log.log(getClass(), 
					leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_ESTADO_UPDATE_ERROR), 
					Log.ERROR, 
					e
				);
		}
		
		respuesta.setTipo(Respuesta.ERROR);
		respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_ESTADO_UPDATE_ERROR));
		
		return respuesta;
	}
	
	/**
	 * Devuelve una lista con todas las solicitudes de préstamo realizadas.
	 * 
	 * @return lista de solicitudes.
	 */
	public Object obtenerSolicitudes(){
		List<Prestamo> prestamos = prestamoDao.obtenerTodos();
		
		return convertToPrestamoList(prestamos);
	}
	
	/**
	 * Devuelve una lista con todas las solicitudes de préstamos realizadas por
	 * el usuario con la cédula pasada como parámetro.
	 * 
	 * @param cedula Identificación del usuario.
	 * @return lista de solicitudes realizadas por el usuario consultado.
	 */
	public Object prestamosUsuario(String cedula){
		List<Prestamo> prestamos = prestamoDao.obtenerPrestamosUsuario(cedula);
		
		return convertToPrestamoList(prestamos);
	}
	
	/**
	 * Devuelve la información relacionada a un préstamo, identificado con el
	 * código pasado como parámetro.
	 * 
	 * @param id Identificación del préstamo.
	 * @return información del préstamo consultado.
	 */
	public Object obtenerPrestamo(int id) {
		try {
			Prestamo prestamo = prestamoDao.obtenerPorId(id);
			return prestamo;
		} catch (RegistryNotFoundException e) {
			Log.log(getClass(), leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS,ConstantesEstaticas.PRESTAMO_INVALID_ID), Log.ERROR, e);
		}
		
		Respuesta respuesta = new Respuesta();
		respuesta.setTipo(Respuesta.ERROR);
		respuesta.setMensaje(leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_INVALID_ID));
		return respuesta;
		
	}
	
	/**
	 * Cancela todas las solicitudes pendientes de aprobar o los préstamos
	 * que aún no han iniciado para el dispositivo dado de baja, con el id
	 * pasado como parámetro.
	 * 
	 * @param idDispositivo Identificación del dispositivo dado de baja.
	 * @return respuesta con el resultado de la operación.
	 */
	public Object cancelarSolicitudes(int idDispositivo) {
		DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
		String fecha = dateFormat.format(new Date());
		
		List<Prestamo> prestamos =  prestamoDao.obtenerSolicitudesPorDispositivo(idDispositivo, fecha);
		
		for (Prestamo prestamo : prestamos){
			
			EstadoPrestamo estado = new EstadoPrestamo();
			estado.setId(ConstantesEstaticas.ESTADO_PRESTAMO_CANCELADO_ID);
			estado.setEstado(ConstantesEstaticas.ESTADO_PRESTAMO_CANCELADO);
			prestamo.setEstadoPrestamo(estado);
			
			try {
				prestamoDao.actualizar(prestamo);
			} catch (DatabaseException e) {
				Log.log(getClass(), 
						leerPropiedades.leerPropiedad(LeerPropiedades.PROPERTIES_RESPUESTAS, ConstantesEstaticas.PRESTAMO_SOLICITUD_ERROR_BD), 
						Log.ERROR, 
						e
					);
			}
		}
		
		return true;
	}
	
	/**
	 * Recibe un objeto tipo List con ítems de tipo dispositivo, y lo convierte
	 * en un objeto PrestamoList.
	 * 
	 * @param prestamos Lista de prestamos, de tipo List.
	 * @return objeto de tipo PrestamoList con los ítems de la lista original.
	 */
	private PrestamoList convertToPrestamoList(List<Prestamo> prestamos) {
		PrestamoList prestamosList =  new PrestamoList();
		
		for (Prestamo prestamo : prestamos)
			prestamosList.add(prestamo);
		
		return prestamosList;
	}

}
